﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Qadir.Collections.Generic;

namespace Qadir
{
    /// <summary>
    /// A class unique to Qadir that allows for dynamic operations to be carried out on an arbitrary object.
    /// </summary>
    public class QadirObject<T>
    {
        /// <summary>
        /// Initializes the QadirObject, wrapped around the provided object.
        /// </summary>
        /// <param name="baseObject">The object on whose part this instance will act.</param>
        public QadirObject(T baseObject)
        {
            BaseObject = baseObject;
        }

        /// <summary>
        /// A dynamic delegate class allowing you to run any action on an object of type T and receive a U as a result.
        /// </summary>
        /// <typeparam name="T">The type with which this Manager was instantiated.</typeparam>
        /// <typeparam name="U">A return type for the delegate.</typeparam>
        /// <param name="arg">An object to the type with which this Manager was instantiated.</param>
        /// <returns>Returns a U after applying specified actions to T "arg".</returns>
        public delegate U ActionOnT<U>(T arg);

        /// <summary>
        /// Evaluates a given expression as being true or false for a given T arg.
        /// </summary>
        /// <param name="arg">The T to evaluate.</param>
        /// <returns>Returns true if the T matches the conditions contained within the delegate.</returns>
        public delegate bool EvaluateForT(T arg);

        /// <summary>
        /// The object that this instance is a wrapper around.
        /// </summary>
        public T BaseObject { get; set; }

        /// <summary>
        /// The type of this instance's base object.
        /// </summary>
        public Type BaseObjectType { get { return BaseObject.GetType(); } }

        /// <summary>
        /// Gets the value of a property of the base object.
        /// </summary>
        /// <param name="name">The name of the property to set.</param>
        /// <param name="type">A Type object that will contain the reflected type of the property.</param>
        /// <param name="index">Optional index values for indexed properties. The indexes of indexed properties are zero-based. This value should be null for non-indexed properties.</param>
        /// <param name="caseSensitive">Specifies whether the method name should be matched to case.</param>
        /// <returns>The value of the property, or null. If no method is found, type will be set to typeof(object).</returns>
        public object GetProperty(string name, out Type type, bool caseSensitive = true, object[] index = null)
        {
            foreach (PropertyInfo property in BaseObjectType.GetProperties())
            {
                bool canGet = ((property.Name == name) || (!caseSensitive && (property.Name.ToLower().Trim() == name.ToLower().Trim())));
                if (canGet)
                {
                    type = property.PropertyType;
                    return property.GetValue(BaseObject, index);
                }
            }
            type = typeof(object);
            return null;
        }

        /// <summary>
        /// Gets the value of a property of the base object.
        /// </summary>
        /// <param name="name">The name of the property to set.</param>
        /// <param name="type">A Type object that will contain the reflected type of the property.</param>
        /// <param name="index">Optional index values for indexed properties. The indexes of indexed properties are zero-based. This value should be null for non-indexed properties.</param>
        /// <param name="caseSensitive">Specifies whether the method name should be matched to case.</param>
        /// <returns>The value of the property, or null. If no method is found, type will be set to typeof(object).</returns>
        public U GetProperty<U>(string name, bool caseSensitive = true, object[] index = null)
        {
            foreach (PropertyInfo property in BaseObjectType.GetProperties())
            {
                bool canGet = ((property.PropertyType == typeof(U)) && ((property.Name == name) || (!caseSensitive && (property.Name.ToLower().Trim() == name.ToLower().Trim()))));
                if (canGet)
                    return (U)property.GetValue(BaseObject, index);
            }
            return default(U);
        }

        /// <summary>
        /// Creates a new U with properties copied from the base object.
        /// </summary>
        /// <typeparam name="U">The type of object to create.</typeparam>
        /// <returns>Returns a new U with compatible properties copied to each other.</returns>
        /// <param name="caseSensitive">A boolean value determining if property and field searching should be case-sensitive.</param>
        /// <remarks>Feel free to override this; it's not nearly perfect.</remarks>
        public virtual U LooseConvert<U>(bool caseSensitive = true)
        {
            U result = (U)Activator.CreateInstance<U>();
            FieldInfo[] uFields = typeof(U).GetFields();
            FieldInfo[] tFields = typeof(T).GetFields();
            Type tFoo;
            foreach (FieldInfo uField in uFields)
            {
                foreach (FieldInfo tField in tFields)

                    if (tField.FieldType == uField.FieldType && string.Compare(tField.Name, uField.Name, !caseSensitive) == 0)
                        uField.SetValue(result, tField.GetValue(BaseObject));
            }
            QadirObject<U> qResult = new QadirObject<U>(result);
            PropertyInfo[] uProperties = typeof(U).GetProperties();
            PropertyInfo[] tProperties = typeof(T).GetProperties();
            foreach (PropertyInfo uProperty in uProperties)
            {
                foreach (PropertyInfo tProperty in tProperties)

                    if (tProperty.PropertyType == uProperty.PropertyType && string.Compare(tProperty.Name, uProperty.Name, !caseSensitive) == 0)
                        qResult.SetProperty(uProperty.Name, GetProperty(uProperty.Name, out tFoo));
            }
            return result;
        }

        /// <summary>
        /// Copies the properties of the base object's type satisfying the given condition to a new array.
        /// </summary>
        /// <param name="condition">A condition that evaluates an expression to be true or false for a given object.</param>
        /// <param name="index">Optional index values for indexed properties. The indexes of indexed properties are zero-based. This value should be null for non-indexed properties.</param>
        /// <returns>Returns a new array containing all properties of the base object's type satisfying the given condition.</returns>
        public object GetPropertiesWhere(QadirObject<PropertyInfo>.EvaluateForT condition, object[] index = null)
        {
            Manager<object> result = new Manager<object>();
            foreach (PropertyInfo property in BaseObjectType.GetProperties())
            {
                if (condition(property))
                    result.Add(property.GetValue(BaseObject, index));
            }
            return result.ToArray();
        }

        /// <summary>
        /// Sets or adds the value of a property of the base object.
        /// </summary>
        /// <param name="name">The name of the property to be set.</param>
        /// <param name="value">The value to be set.</param>
        /// <param name="index">Optional index values for indexed properties. The indexes of indexed properties are zero-based. This value should be null for non-indexed properties.</param>
        /// <param name="caseSensitive">Specifies whether the method name should be matched to case.</param>
        /// <returns>Returns true if save was successful, or false.</returns>
        public bool SetProperty(string name, object value, bool caseSensitive = true, object[] index = null)
        {
            foreach (PropertyInfo property in BaseObjectType.GetProperties())
            {
                bool canSet = ((property.Name == name) || (!caseSensitive && (property.Name.ToLower().Trim() == name.ToLower().Trim())));
                if (canSet)
                {
                    property.SetValue(BaseObject, value, index);
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Sets or adds the value of a property of the base object.
        /// </summary>
        /// <typeparam name="T">The type of the property to be set.</typeparam>
        /// <param name="name">The name of the property to be set.</param>
        /// <param name="value">The value to be set.</param>
        /// <param name="index">Optional index values for indexed properties. The indexes of indexed properties are zero-based. This value should be null for non-indexed properties.</param>
        /// <param name="caseSensitive">Specifies whether the method name should be matched to case.</param>
        /// <returns>Returns true, if save was successful.</returns>
        public bool SetProperty<U>(string name, U value, bool caseSensitive = true, object[] index = null)
        {
            foreach (PropertyInfo property in BaseObjectType.GetProperties())
            {
                bool canSet = ((property.PropertyType == typeof(U)) && ((property.Name == name) || (!caseSensitive && (property.Name.ToLower().Trim() == name.ToLower().Trim()))));
                if (canSet)
                {
                    property.SetValue(BaseObject, value, index);
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Invokes the specified method on the instance, and returns a value.
        /// </summary>
        /// <param name="name">The name of the method to run.</param>
        /// <param name="arguments">Any arguments with which to invoke the function. Can be null.</param>
        /// <param name="caseSensitive">Specifies whether the method name should be matched to case.</param>
        /// <param name="type">A Type object that will contain the reflected type of the method.</param>
        /// <returns>Returns the return value of the first recognized method, or null if the method returns void or no method is invoked.</returns>
        public object RunMethod(string name, out Type type, bool caseSensitive = true, object[] arguments = null)
        {
            foreach (MethodInfo method in BaseObjectType.GetMethods())
            {
                bool canRun = ((method.Name == name) || (!caseSensitive && (method.Name.ToLower().Trim() == name.ToLower().Trim())));
                if (canRun)
                {
                    type = method.ReturnType;
                    return method.Invoke(BaseObject, arguments);
                }
            }
            type = typeof(object);
            return null;
        }

        /// <summary>
        /// A pointer to the base object.
        /// </summary>
        public unsafe IntPtr Pointer
        {
            get
            {
                return new IntPtr(System.Reflection.Pointer.Unbox(BaseObject));
            }
        }
    }
}
